home *** CD-ROM | disk | FTP | other *** search
- INTRODUCCION AL ASM: LAS INTERRUPCIONES
- =======================================
-
- Como comentábamos al final del capítulo dedicado a la pila, en este capítulo
- vamos a abordar el tema de las interrupciones. Comencemos pues.
-
- Al principio del curso, vimos el funcionamiento del uP, que contínuamente
- recoge instrucciones de la memoria (de la dirección apuntada por CS:IP) y las
- ejecuta. Aunque éste es el funcionamiento habitual, hay momentos en los que
- esta actividad se tiene que interrumpir. Para ver la razón de la existencia de
- las interrupciones, veamos un ejemplo.
- Supongamos que el funcionamiento del teclado fuese el siguiente: el programa
- enviaría una señal al teclado, a la que el teclado respondería con un código
- indicativo de la tecla. Existiría también un código para indicar 'ninguna
- tecla pulsada'. Dentro de este esquema de funcionamiento, imaginemos que un
- programa está en un momento dado llevando a cabo una actividad de cálculo, sin
- comprobar para nada lo que ocurre con el teclado. Es perfectamente posible que
- en ese momento el usuario del ordenador pulse una tecla y la suelte antes de
- que el programa compruebe el teclado; si el funcionamiento del teclado fuese el
- que hemos visto, esa pulsación de tecla se perdería. Existen varias posibles
- soluciones a este problema: una sería que el programa comprobase constantemente
- el teclado, con la consiguiente molestia al desarrollar el programa y la pérdi-
- da de velocidad. Otra sería añadir hardware al teclado para llevar un buffer
- con las teclas pulsadas. Esto ocurre así en los PCs, pero este buffer sólo
- tiene capacidad para unas cuatro pulsaciones, con lo que el problema sigue pre-
- sentándose. La solución más adecuada, ya que se puede extender a otros proble-
- mas parecidos, son las interrupciones hardware, que además es la solución
- implementada en los PCs.
-
- Cuando algún periférico necesita que el uP se encargue de algo con urgencia
- (por ejemplo, leer el valor de una tecla) pone a nivel activo uno de los pines
- del uP, denominado INTR. En este momento, el uP para lo que está haciendo (si
- está en medio de la ejecución de una instrucción, se completa ésta primero) y
- lee el valor de 8 bits (0 a 255d) de las 8 líneas bajas del bus de datos. El
- dispositivo que provoca la interrupción se debe haber encargado de poner en
- éstas líneas un código que identifica el número de interrupción. Este número es
- un código que identifica el dispositivo que ha provocado la interrupción; por
- ejemplo, el teclado tiene el número 9 como identificativo y por tanto pone en
- las 8 líneas bajas del bus de datos el valor 00001001b cuando se pulsa una te-
- cla. Ahora, lo que ocurre es lo siguiente: el uP no está especialmente diseñado
- para manejar algunos dispositivos en concreto, por lo que cuando recibe una in-
- terrupción lo que hace es llamar a una rutina que el programador ha dejado re-
- sidente en la memoria, que es la que lee la tecla pulsada y la almacena en un
- buffer en memoria en el caso de la interrupción 9 (para otros números de inte-
- rrupción, se lleva a cabo otra tarea). Esta rutina se suele denominar 'rutina
- de servicio a la interrupción', por razones bastante claras (eso espero). La
- última instrucción de la rutina de servicio a la interrupción es la instrucción
- IRET, que hace que el uP vuelva a la dirección donde estaba antes de la inte-
- rrupción. Aún nos queda por ver cómo el uP, a partir del número recibido por el
- bus de datos con el número de interrupción, identifica a qué rutina ha de
- saltar.
-
- Los diseñadores del 8086 reservaron los primeros 4*256d bytes de la memoria
- (1 kilobyte desde la dirección absoluta 00000h hasta la 003FFh, ambas incluí-
- das) para una tabla denominada 'tabla de vectores de interrupción'. Esta tabla
- contiene cuatro bytes para cada número de interrupción (por eso son 4*256d
- bytes, 4 bytes/nº de interrupcion * 256d números de interrupción posibles), que
- especifican la dirección de memoria donde comienza la rutina de servicio a cada
- interrupción. Al recibir una demanda de interrupción, el micro toma el valor
- del bus de datos (el nº de interrupción) y lo multiplica por cuatro. Usa el va-
- lor obtenido como dirección de la que leer 4 bytes con la dirección a la que
- saltar. Estos cuatro bytes son el offset y el segmento que forman la dirección
- absoluta. El ordenamiento Intel especifica que se almacenan primero los bytes
- de menor peso, y en el caso del 8086 se almacena primero el offset que el valor
- de segmento. Por ejemplo, si instalásemos una rutina de servicio a la interrup-
- ción CCh en la dirección de memoria 1122h:3344h, tendríamos que introducir los
- siguientes valores en la tabla de vectores de interrupción:
-
- DIRECCION ABSOLUTA VALOR
- ------------------ -----
- 4 * CCh = 816d 44h
- 4 * CCh + 1 = 817d 33h
- 4 * CCh + 2 = 818d 22h
- 4 * CCh + 3 = 819d 11h
-
- Este es el modelo de funcionamiento, pero todavía no hemos vistos varios
- detalles muy importantes. En realidad, la arquitectura del PC hace que los
- periféricos no interrumpan directamente al uP (¿qué ocurriría si dos periféri-
- cos deciden generar una señal de interrupción a la vez?), sino que éstas están
- jerarquizadas mediante unos chips denominados 8259 o PIC ('Programmable
- interrupt controller', Controlador de Interrupciones Programable), interactuan-
- do de una manera bastante compleja. De momento, esto no nos interesa demasiado.
-
- Cuando el micro recibe la señal de interrupción, el CS y el IP contienen la
- dirección de la siguiente instrucción a ejecutar (ya que el IP se incrementa
- nada más cargar la instrucción, antes incluso de ejecutarla). Estos se cargan
- con los valores leídos de la tabla de vectores de interrupción, por lo que es
- necesario preservar su valor actual para luego volver al punto donde se estaba
- cuando se produjo la interrupción. Lo que se hace es empujar estos valores en
- la pila antes de saltar a la rutina de servicio a la interrupción. Además,
- antes de empujar el CS y el IP (en este orden), se empuja el contenido de un
- registro que todavía no hemos visto (lo veremos dentro de poco), llamado
- 'registro de flags'. Los bits de este registro tienen cada uno un significado
- especial, indicando en todo momento el estado actual del uP y, en el caso de
- algunos bits, el resultado de la última operación ejecutada (por ejemplo, el
- flag ZERO indica si la última operación dio un resultado de 0). Ya que algunos
- flags se utilizan para indicar si se esta ejecutando una rutina de servicio a
- la interrupción (y por tanto se modifican antes de saltar a la rutina), este
- registro se salva a fin de que al volver al hilo normal de ejecución del pro-
- grama se restaure completamente el estado anterior de micro.
-
- Ya que una interrupción se puede producir en cualquier momento, ésta debe
- ser transparente al programa en ejecución. Cuando se escribe un programa, no
- se piensa en que entre una instrucción y la siguiente pueden ejecutarse 200
- instrucciones que leen un valor de teclado y lo almacenan en un buffer en
- memoria. Por tanto, al volver de una interrupción se tiene que restaurar por
- completo el estado del uP, de forma que el programa interrumpido no se vea
- afectado. Por ejemplo, supongamos un fragmento de código así:
-
- MOV AX,[200h]
- ADD AX,30h
-
- Si en medio de las dos instrucciones se ejecuta una rutina de servicio a la
- interrupción que modifica el contenido de AX, el resultado para el programa
- puede ser catastrófico. El uP se encarga de que el registro de flags sea res-
- taurado, ya que siempre es modificado. Pero es la propia rutina de servicio a
- la interrupción la que debe preservar el contenido de los registros que ésta
- modifique. Por tanto, las rutinas de servicio a la interrupción suelen tener el
- siguiente aspecto:
-
- PUSH AX
- PUSH BX
- PUSH CX
- PUSH DX
- PUSH DS
- PUSH ES
- PUSH BP
- PUSH SI
- PUSH DI
-
- [...] ;código que modifica los anteriores 9 registros
-
- POP DI
- POP SI
- POP BP
- POP ES
- POP DS
- POP DX
- POP CX
- POP BX
- POP AX
- IRET ;retorna al programa interrumpido
-
- Otro de los aspectos importantes de las interrupciones es el de la pila.
- Hemos visto que el uP empuja el contenido de los flags, el de CS y el de IP en
- la pila apuntada por SS:SP en el momento de la interrupción. Aunque la rutina
- de servicio a la interrupción podría modificar SS y SP para usar su propia
- pila, esto no es lo habitual; es decir, las rutinas de servicio a la interrup-
- ción suelen utilizar la pila del programa interrumpido. Por tanto, aunque vimos
- que un POP no borra el valor apuntado por SS:SP después de leerlo, no se puede
- suponer que después de un POP los valores inmediatamente por debajo de SS:SP
- continúen igual, ya que pueden haber sido pisados debido a una interrupción.
-
- En realidad, cuando se pone a nivel activo la patilla INTR, el uP no siempre
- interrumpe el programa en ejecución, sino que primero consulta uno de los bits
- del registro de flags, llamado IF ('Interruption Flag', Flag de Interrupción)
- y sólo si este está a 1 se atiende la interrupción. En caso de que IF está a 0,
- simplemente se ignora la petición de interrupción. Es el 8259 (el PIC) el que
- se encarga de volver a generar la interrupción repetidas veces hasta que el uP
- está listo. Cuando se salta a una rutina de servicio a la interrupción, una de
- las cosas que se hace es poner este bit a 1 de manera que no se pueda interrum-
- pir la propia rutina de servicio a la interrupción. Pero esta característica la
- puede aprovechar cualquier programa, ya que existen dos instrucciones que ponen
- a 0 o a 1 el flag IF:
-
- STI Pone a 1 el bit IF, para que se atienda a las
- peticiones de interrupción.
- CLI Pone a 0 el bit IF, para desactivar las interrupciones.
-
- Debido a que se puede controlar el uP para que atienda o no a las peticiones
- de servicio a la interrupción, este tipo de interrupciones se denominan
- 'Interrupciones enmascarables'; existe otra patilla denominada 'NMI' ('Non
- Maskable Interrupt', Interrupción No Enmascarable) de función parecida a INTR,
- pero a cuya petición el uP siempre responde. En realidad, no sé con certeza
- cómo funcionan las NMI, ya que no se usan habitualmente para la programación.
- En el Spectrum, se utilizaba esta interrupción para los 'transfer', aparatos
- que permitian en cualquier momento generar una NMI, por medio de un pulsador, y
- que se grabara a cinta o disco los registros del uP y los contenidos de toda la
- memoria (con cualquier programa que ésta tuviera), para luego poder cargarlo y
- continuar la ejecución en el mismo punto; estos eran copiadores infalibles.
-
- Todo esto que hemos visto hasta ahora es interesante para nosotros, pero en
- realidad es al diseñador de hardware y al programador de drivers de dispositi-
- vos a quien más le concierne. Además, las interrupciones hardware son aún más
- complicadas, ya que hay que controlar el PIC, etc... En realidad, sólo en algu-
- nos pocos casos nos interesará todo esto (el caso más habitual es el de querer
- instalar una rutina de servicio a la interrupción de teclado para poder detec-
- tar la pulsación simultánea de varias teclas, como en los juegos). Estas inte-
- rrupciones que hemos visto se denominan interrupciones hardware, pero lo que
- principalmente nos interesa son las interrupciones software.
-
- Existe una instrucción en ASM llamada INT, que fuerza al uP a realizar el
- mismo proceso que lleva a cabo para atender una interrupción. El número de
- interrupción no se lee del bus de datos, sino que viene dado por el operando de
- la instrucción INT. Por ejemplo, puede usarse la instrucción 'INT 9' para lla-
- mar a la rutina de servicio a la interrupción de teclado. En realidad, éste no
- es el uso habitual, ya que, por ejemplo, la rutina de servicio a la interrup-
- ción 9 espera que el teclado tenga una pulsación de tecla que enviar, por lo
- que el funcionamiento de ésta sería anómalo al invocar la INT 9 sin ninguna te-
- cla esperando. En realidad, la utilidad de las interrupciones software es otra.
-
- Una parte muy importante de un sistema operativo (quizá la más importante)
- es el 'kernel' o núcleo. Este kernel no es más que un conjunto de rutinas o
- funciones que ejecutan diversas tareas, como por ejemplo escribir una cadena en
- el monitor o escribir datos en un fichero. Estas funciones pueden estar en dis-
- tintas direcciones de memoria en distintos ordenadores, por lo que es necesaria
- una manera de encontrarlas. En el caso del MS-DOS, la mayoría de los servicios
- del kernel están integrados en una función que lleva a cabo distintas acciones
- según el contenido del registro AH a la entrada; a cada tarea distinta que se
- puede ejecutar en función de AH se le denomina 'servicio'. En los demás regis-
- tros se pasan parámetros, cuyo significado varía de un servicio a otro; en caso
- de que sea necesario devolver algún valor, se devuelve en los registros. Por
- ejemplo, el servicio 9 (AH = 9) imprime una cadena en la pantalla. La cadena se
- recoge de la dirección dada por el par DS:DX, y el final de ésta se indica con
- el carácter '$'. La función que ofrece todos estos servicios se instala en
- memoria cuando se carga el sistema operativo de los ficheros IO.SYS y
- MSDOS.SYS. Pero cada vez que se carga puede hacerse en una dirección distinta,
- por lo que se almacena en el vector número 21h de la tabla de vectores de inte-
- rrupción la dirección donde reside la rutina. Por lo tanto, cuando un programa
- quiere invocar algún servicio lo que se hace es ejecutar una instrucción 'INT
- 21h'. A modo de ejemplo, veamos qué haríamos para imprimir una cadena almacena-
- da en la dirección 1234h:ABCDh :
-
- PUSH DS ; preserva el contenido de DS
- MOV AX,1234h ; segmento de la dir. de la cadena
- MOV DS,AX ; en DS
- MOV DX,ABCDh ; offset de la dir. de la cadena
- MOV AH,9 ; servicio 9: escribir cadena
- INT 21h ; invoca la función del DOS
- POP DS ; restaura DS
-
- La interrupción 21h nunca se invoca a causa de una interrupción hardware,
- por lo que no se ejecuta entre dos instrucciones cualquiera. Cuando ésta se
- ejecuta, es debido a que el programa la ha invocado expresamente. Por tanto, no
- es necesario preservar completamente el estado del uP, y de hecho no se hace.
- Muchos de los servicios de la interrupción 21h devuelven valores en los regis-
- tros, por lo que necesariamente son modificados; en los servicios de ficheros
- un flag se utiliza para indicar si ha habido un error, y en caso de que lo haya
- habido AX contiene un código de error. SS y SP siempre se conservan (por el
- propio funcionamiento de las interrupciones, si la rutina modificase estos re-
- gistros no se podría volver al programa), pero los demás registros no siempre
- lo hacen. En los manuales de referencia de las interrupciones del DOS suele
- venir especificado qué registros se conservan, pero lo habitual es empujar los
- registros cuyo contenido interesa conservar en lugar de consultar la referen-
- cia. A no ser que se use para devolver algún valor, se suele suponer que DS se
- conserva. Si interesa conservar el contenido de algún otro registro, es mejor
- empujarlo en la pila.
-
- El BIOS también es, principalmente, un conjunto de funciones que el progra-
- mador puede usar. Las funciones son de más bajo nivel que las del DOS, y de
- hecho las funciones del DOS suelen valerse de llamadas a la BIOS para cumplir
- su tarea. La BIOS puede variar de una placa a otra, pero siempre debe propor-
- cionar algunas funciones que vienen definidas por el estándar PC, como por
- ejemplo la función para escribir un carácter en la pantalla o la función para
- poner un pixel a un color determinado, usada en modo gráfico. Las funciones
- gráficas del BIOS están agrupadas en la INT 10h, siendo AH el que especifica el
- servicio o función en concreto, y, en algunas funciones, AL la subfunción.
-
- Con lo que ya sabemos, podemos utilizar las interrupciones del BIOS y del
- DOS sin más problema. Al igual que para programar en un lenguaje como Pascal o
- C es tan importante conocer la sintáxis como las funciones que nos da la
- librería estándar de nuestro compilador, para programar en ASM también es nece-
- sario conocer las interrupciones del BIOS y el DOS. Que yo sepa, nadie se
- aprende los servicios de memoria, por lo que lo habitual es programar con un
- manual de referencia de las interrupciones. Por poco dinero, encontraréis en
- cualquier librería un libro con estos datos. Se hace casi imprescindible para
- programar en ASM, por lo que os recomiendo que os hagáis con uno. Además, a
- partir de ahora escribiremos programitas en ASM que habrá que ensamblar y
- linkar, por lo que os hará falta el Macro Assembler de Microsoft (abreviadamen-
- te MASM) o el Turbo Assembler de Borland (TASM). No os voy a recomendar ninguno
- personalmente, ya que las características principales de ambos son iguales,
- pero debería transmitiros la opinión generalizada del área de programación ASM
- de FidoNET, que se decanta claramente por el TASM. De todas maneras, en el
- curso no usaremos características particulares de ninguno de los dos, por lo
- que podréis usar cualquiera de los dos para seguirlo.
-
- En el capítulo que viene veremos el 'Hello, world' en ASM, con el objetivo
- principal de ver el esqueleto de un listado ASM para el MASM o el TASM. Segui-
- remos después con el juego de instrucciones, viendo los flags y los saltos
- primero. Cuando conozcamos la mayoría de las instrucciones, comenzaremos a ver
- las tareas principales de cualquier programa: interacción con el usuario,
- manejo de ficheros, gestión de memoria, etc... Si alguien tiene interés en al-
- gún tema en especial, no tiene más que comentármelo. Personalmente, me gustaría
- ver algo de programación gráfica, que es mi hobby preferido, pero sólo si hay
- gente interesada. Dentro de unos pocos capítulos, iremos viendo a la vez
- nuevas instrucciones y servicios que proporcionan el DOS y el BIOS, ya que esto
- es un curso y no una referencia de ASM. Al igual que hasta ahora, no busco dar
- una definición teórica del ASM (esto no es la Universidad), sino que quiero que
- quien siga el curso pueda hacer en ASM todo lo que hace en otros lenguajes.
-
- Salut :-)
-
- Jon